add an expansion limit for entities
authorDebian Qt/KDE Maintainers <debian-qt-kde@lists.debian.org>
Thu, 16 Apr 2020 17:05:12 +0000 (18:05 +0100)
committerDmitry Shachnev <mitya57@debian.org>
Thu, 16 Apr 2020 17:05:12 +0000 (18:05 +0100)
Origin: upstream, https://code.qt.io/cgit/qt/qtbase.git/commit/?id=fd4be84d23a0db41
Last-Update: 2020-02-27

Recursively defined entities can easily exhaust all available
memory. Limit entity expansion to a default of 4096 characters to
avoid DoS attacks when a user loads untrusted content.

Added a setter and getter to allow modifying the expansion limit.

QXmlStreamReader does now by default limit the expansion of entities
to 4096 characters. Documents where a single entity expands to more
characters than the limit are not considered well formed. The limit
is there to avoid DoS attacks through recursively expanding entities
when loading untrusted content. The limit can be changed through the
QXmlStreamReader::setEntityExpansionLimit() method.

Gbp-Pq: Name CVE-2015-9541.diff

src/corelib/serialization/qxmlstream.cpp
src/corelib/serialization/qxmlstream.g
src/corelib/serialization/qxmlstream.h
src/corelib/serialization/qxmlstream_p.h

index 827996ee2d77ff04b99fe2dbffa94770ee25dff4..ea8b07c8831fbfa156363daf2e3c9b374c94ff84 100644 (file)
@@ -2037,6 +2037,42 @@ QStringRef QXmlStreamReader::dtdSystemId() const
    return QStringRef();
 }
 
+/*!
+  \since 5.15
+
+  Returns the maximum amount of characters a single entity is
+  allowed to expand into. If a single entity expands past the
+  given limit, the document is not considered well formed.
+
+  \sa setEntityExpansionLimit
+*/
+int QXmlStreamReader::entityExpansionLimit() const
+{
+    Q_D(const QXmlStreamReader);
+    return d->entityExpansionLimit;
+}
+
+/*!
+  \since 5.15
+
+  Sets the maximum amount of characters a single entity is
+  allowed to expand into to \a limit. If a single entity expands
+  past the given limit, the document is not considered well formed.
+
+  The limit is there to prevent DoS attacks when loading unknown
+  XML documents where recursive entity expansion could otherwise
+  exhaust all available memory.
+
+  The default value for this property is 4096 characters.
+
+  \sa entityExpansionLimit
+*/
+void QXmlStreamReader::setEntityExpansionLimit(int limit)
+{
+    Q_D(QXmlStreamReader);
+    d->entityExpansionLimit = limit;
+}
+
 /*!  If the tokenType() is \l StartElement, this function returns the
   element's namespace declarations. Otherwise an empty vector is
   returned.
index 10bfcd491cb77b8ec65d7f6e49cf8f40163827a7..5726bafb265c14c084ec8c361181fbaae57ae5c0 100644 (file)
@@ -277,9 +277,19 @@ public:
     QHash<QStringView, Entity> entityHash;
     QHash<QStringView, Entity> parameterEntityHash;
     QXmlStreamSimpleStack<Entity *>entityReferenceStack;
+    int entityExpansionLimit = 4096;
+    int entityLength = 0;
     inline bool referenceEntity(Entity &entity) {
         if (entity.isCurrentlyReferenced) {
-            raiseWellFormedError(QXmlStream::tr("Recursive entity detected."));
+            raiseWellFormedError(QXmlStream::tr("Self-referencing entity detected."));
+            return false;
+        }
+        // entityLength represents the amount of additional characters the
+        // entity expands into (can be negative for e.g. &amp;). It's used to
+        // avoid DoS attacks through recursive entity expansions
+        entityLength += entity.value.size() - entity.name.size() - 2;
+        if (entityLength > entityExpansionLimit) {
+            raiseWellFormedError(QXmlStream::tr("Entity expands to more characters than the entity expansion limit."));
             return false;
         }
         entity.isCurrentlyReferenced = true;
@@ -830,6 +840,8 @@ entity_done ::= ENTITY_DONE;
 /.
         case $rule_number:
             entityReferenceStack.pop()->isCurrentlyReferenced = false;
+            if (entityReferenceStack.isEmpty())
+                entityLength = 0;
             clearSym();
         break;
 ./
index d30c6bc01f556bd557577de250c380b352502f47..9aae4836c09da199ab0fce7a7eab523189e150f7 100644 (file)
@@ -432,6 +432,8 @@ public:
     QStringRef dtdPublicId() const;
     QStringRef dtdSystemId() const;
 
+    int entityExpansionLimit() const;
+    void setEntityExpansionLimit(int limit);
 
     enum Error {
         NoError,
index 61f501f81b7db0e2179a15bca4365e6f921964aa..31053f8e0b954fe3bb137190c092a5a9ed888fb0 100644 (file)
@@ -774,9 +774,19 @@ public:
     QHash<QStringView, Entity> entityHash;
     QHash<QStringView, Entity> parameterEntityHash;
     QXmlStreamSimpleStack<Entity *>entityReferenceStack;
+    int entityExpansionLimit = 4096;
+    int entityLength = 0;
     inline bool referenceEntity(Entity &entity) {
         if (entity.isCurrentlyReferenced) {
-            raiseWellFormedError(QXmlStream::tr("Recursive entity detected."));
+            raiseWellFormedError(QXmlStream::tr("Self-referencing entity detected."));
+            return false;
+        }
+        // entityLength represents the amount of additional characters the
+        // entity expands into (can be negative for e.g. &amp;). It's used to
+        // avoid DoS attacks through recursive entity expansions
+        entityLength += entity.value.size() - entity.name.size() - 2;
+        if (entityLength > entityExpansionLimit) {
+            raiseWellFormedError(QXmlStream::tr("Entity expands to more characters than the entity expansion limit."));
             return false;
         }
         entity.isCurrentlyReferenced = true;
@@ -1308,6 +1318,8 @@ bool QXmlStreamReaderPrivate::parse()
 
         case 10:
             entityReferenceStack.pop()->isCurrentlyReferenced = false;
+            if (entityReferenceStack.isEmpty())
+                entityLength = 0;
             clearSym();
         break;